package jupiter.message;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.sql.Date;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jupiter.servlets.JabberServlet;
import jupiter.servlets.MessageServlet;
import jupiter.util.Application;
import jupiter.util.DBConnection;
import jupiter.util.EventSubscriber;
import jupiter.util.MemCache;
import jupiter.util.Timey;
import jupiter.channel.Address;
import jupiter.channel.Channel;
import jupiter.channel.ChannelInterface;
import jupiter.channel.Domain;
import jupiter.channel.Global;
import jupiter.channel.Session;
import jupiter.filters.FilterException;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mysql.jdbc.Statement;

import javax.jms.MessageListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/*
{
  type: "channel"
  action: "create"
  from: "jmvc.com@jabbify.com"
  to: "justin_presence#jmvc.com@jabbify.com"
  data : {
    only:  ['hank','john'] //only allow these channels to add listener
    block: ['fred'] //do not allow these channels to add listener
    subscriptions: ['jmvc'] //which channels I subscribe to
  }
}
 */

public class Message implements Serializable{
	static Logger logger = LoggerFactory.getLogger(Message.class);
	
	private String message;
	public JSONObject json;
	private Address _to;
	private Address _from;
	public String _type;
	public String _action;
	private String _page;
	// used to cache json strings
	public String _cached_json_string;
	public HttpServletRequest request;
	public JSONObject data;
	public java.util.Date date;
	
	public Message(String mess) throws FilterException {
		message = mess;
		parseAndPrepare(false);
	}
	public Message(String from, String to, String message, String name){
		try {
			_to = new Address(to);
			_from = new Address(from);
		} catch (FilterException e) {
			Message.logger.error("Filter error parsing from/to", e);
		}
		_type = "message";
		_action = "create";
		date = new java.util.Date();
	}
	public Message(String mess, HttpServletRequest req, HttpServletResponse res) throws FilterException, NotRegisteredException {
		message = mess;
		request = req;
		parseAndPrepare(true);
		
		HttpSession session = req.getSession(false);
		if(session != null){

			Session s = (Session) session.getAttribute("sessionChannel");
			if(s.isInvalid()) {
				session.invalidate();
				new FilterException("{error: 'session is invalid'}");
			}
			_from = new Address(s.address().toString());
			try{
				json.put("from", _from.toString());
			}catch(JSONException e){
				
			}
		}else{
			//throw new FilterException("{error: 'no session, cant create message'}");
		}
		
	}
	public void throwIfHasNoFrom() throws FilterException {
		if(_from == null)
			throw new FilterException("{error: 'no session, cant create message'}");
	}
	void parseAndPrepare(Boolean parse_referer) throws FilterException {
		try{
			json = new JSONObject(message);
			if(json.has("to")) 		_to =  new Address(json.getString("to"));
			if(json.has("from")) 	_from =  new Address(json.getString("from"));
			if(parse_referer) { // only need to check page for requests from the browser, not activeMQ
				String page = request.getParameter("source");
				if(page != null) _page = page; // then try the source param
				if(_page == null) _page = request.getHeader("Referer"); // first try referer
				if(_page == null) _page = request.getParameter("Referer"); // first try referer
				if(_page == null) _page = "file"; // default to file
			}
			date = new java.util.Date();
			json.put("createdAt", date.getTime());
			
			_type =  json.getString("type");
			_action =  json.getString("action");
			data = json.getJSONObject("data");
		}catch(JSONException e){	
			Message.logger.error("Error in parse and prepare", e);
		}
	}
	public void to(Address newTo){
		if(newTo == null)
			System.out.println("\n\nWTF!");
		_to = newTo;
		try{ 
			json.put("to", newTo.toString()); 
		}catch(JSONException e){}
	}
	public Address to(){
		return _to;
	}
	public Address from(){
		return _from;
	}
	public void from(Address newTo){
		_from = newTo;
		try{ json.put("from", newTo.toString()); }catch(JSONException e){}
	}
	
	public String type(){
		return _type;
	}
	public void type(String newType){
		_type = newType;
		try{ json.put("type", newType.toString()); }catch(JSONException e){}
	}

	public String action(){
		return _action;
	}
	public void action(String newAction){
		_action = newAction;
		try{ json.put("action", newAction.toString()); }catch(JSONException e){}
	}
	
	public String page(){
		return _page;
	}
	public void page(String newPage){
		_page = newPage;
		try{ json.put("page", newPage.toString()); }catch(JSONException e){}
	}
	public Address address() throws FilterException{

		try{
			return new Address(json.getJSONObject("data").getString("address"));
		}catch(JSONException e){
			throw new FilterException("{error: 'no address in data'}");
		}
	}
	public String secret() throws FilterException{

		try{
			return json.getJSONObject("data").getString("secret");
		}catch(JSONException e){
			throw new FilterException("{error: 'no secret in data'}");
		}
	}
	public String toString(){
		return json.toString();
	}
	private static Pattern domainp = Pattern.compile("https?://(?:www\\.)?([^/]*)([^#\\?]*)");
	/**
	 * Returns the domain name from the Referer string
	 * For example, page could be http://cnn.com/mypage.html, which would return cnn.com
	 * @param page
	 * @return
	 */
	public String domainName(){
		String domainName = "jabbify.com";
		Matcher matcher = domainp.matcher(_page);
		boolean matchFound = matcher.find();
		if(matchFound){
			domainName = matcher.group(1).toLowerCase();
		}else{
			domainName = "file";
		}
		return domainName;
	}
	public void save() throws FilterException{
		try{
			Integer id =  (Integer)(new DBConnection() {
				public Object run() throws SQLException{
					prepareStatement("INSERT INTO messages "+
							"(`type`, `action`, `to`, `from`, `json`, `referer`, `created_at`, `updated_at`) "+
							"VALUE(?, ?, ?, ?, ?, ?, ?,?);", Statement.RETURN_GENERATED_KEYS);
					pstmt.setString(1, type().toString());
					pstmt.setString(2, action().toString());
					pstmt.setString(3, to().toString());
					pstmt.setString(4, from().toString());
					pstmt.setString(5, json.toString() );
					pstmt.setString(6, page().toString() );
					pstmt.setTimestamp(7, new java.sql.Timestamp(date.getTime()));
					pstmt.setTimestamp(8, new java.sql.Timestamp(date.getTime()));
					pstmt.executeUpdate();
					getGeneratedKeys();
					keyRS.next();
					//System.out.println("Generated key: " + keyRS.getInt(1));
					return new Integer(keyRS.getInt(1));
				}
			}).exec();
//			MemCache.mcc.delete("messages:"+to().toString());
			try{
				json.put("id", id);
			}catch(JSONException e){
				Message.logger.error("JSON Error using id", e);
			}
			
		}catch(SQLException sqle){
			System.out.println(sqle);
			throw new FilterException("{error: 'unable to save message'}");
		}
		
	}
	
	

	/**
	 * Returns true if the given Referer is a registered domain, false otherwise
	 * Checks first in the local domainHash, then memcache, then the database
	 * @param page - a Referer string like "http://cnn.com/mypage.html"
	 * 
	 * Could also be a domain that's allowed to send stuff to another domain.  For example, wiki.jmvc.com might 
	 * be on the allowed list for jmvc.com.  In this case, check the domain sent on the "to" string and make sure 
	 * this domain's referer (_page) is on the allowed list for the "to" domain.
	 * @return
	 */
	public Boolean isRegistered(){
		// get the domain name
		String domainName = domainName();
		if(isFileSystem()) return true;
		// check locally
		if(Global.domainPresent(domainName)) return true;
		// check memcache
		Boolean registered = false;
		// check database
		try{
			registered = (Boolean) (new DBConnection() {
				public Object run(Object m) throws SQLException{
					String dn = (String) m;
					prepareStatement("SELECT d.id FROM domains d WHERE d.name = ?;");
					pstmt.setString(1, dn);
					executeQuery();
					String domainId = null;
					while (rs.next()) {
			            domainId = rs.getString("d.id");
			        }
					if(domainId != null) return true;
					return false;
				}
			}).exec(domainName);
		}catch(SQLException sqle){
			Message.logger.error("SQL Error getting domain name", sqle);
		}
		if(registered) return true;
		String destinationDomain = to().domain();
		String rules = null;
		try{
			rules = (String) (new DBConnection() {
				public Object run(Object m) throws SQLException{
					String dn = (String) m;
					prepareStatement("SELECT d.rules FROM domains d WHERE d.name = ?;");
					System.out.println(dn);
					pstmt.setString(1, dn);
					executeQuery();
					String rules = null;
					while (rs.next()) {
						rules = rs.getString("d.rules");
			        }
					return rules;
				}
			}).exec(destinationDomain);
		}catch(SQLException sqle){
			Message.logger.error("SQL Error getting rules", sqle);
		}
		// if domain isnt found, its not registered
		if(rules == null) return false;
		JSONObject r = null;
		JSONArray allowedDomains = null;
		try {
			r = new JSONObject(rules);
			allowedDomains = (JSONArray) r.get("domains_can_create_session");
		} catch (JSONException e) {
			Message.logger.error("domains_can_create_session error", e);
		}
		for(int i=0; i<allowedDomains.length(); i++){
			try {
				if(domainName().equals(allowedDomains.get(i)))
					return true;
			} catch (JSONException e) {
				Message.logger.error("Error getting allowed domains", e);
			}
		}
		return false;
	}

	public void sendToJabber(){
		Boolean send = (Application.getProperty("sendMessages").equals("true"));
		if(send){
			ArrayList<String> users = getJabberUsers();
			Iterator<String> itr = users.iterator();
			while (itr.hasNext()) {
		        String user = itr.next();
				try {
					sendJabberRequest(to().domain(), user, data.getString("body"));
				} catch (JSONException e) {
					Message.logger.error("JSON Error parsing body", e);
				}
			}
		}
	}
	public Boolean isFileSystem(){
		return (domainName().equals("file") || domainName().equals("localhost"));
	}
	private JSONObject getDomainRules(){
		// get the userList for this domain
		String rules = null;
		try{

			rules = (String) (new DBConnection() {
				public Object run(Object domainName) throws SQLException{
					String dn = (String) domainName;
					prepareStatement("Select rules from domains WHERE domains.name = ?;");
					pstmt.setString(1, dn);
					executeQuery();
					boolean has = rs.next();
					if(!has) return null;
					return rs.getString("rules");
				}
			}).exec(to().domain());
		}catch(SQLException sqle){
			Message.logger.error("SQL Exception Error", sqle);
		}
		// parse it into jsonObj
		JSONObject o = null;
		try {
			o = new JSONObject(rules);
		} catch (JSONException e) {
			Message.logger.error("Can't parse rules", e);
		}
		return o;
	}
	/**
	 * Grabs the list of banned users for this domain, returns true if this user is on the list
	 * @return
	 */
	public Boolean isBannedUser(){
		// compare the current address to it
		JSONArray bannedUsers = null;
		try {
			bannedUsers = (JSONArray) getDomainRules().get("banned");
		} catch (JSONException e3) {
			Message.logger.error("Can't parse banned list", e3);
		}
		Address address = null;
		try {
			address = new Address( data.getString("address")  );
		} catch (FilterException e2) {
			Message.logger.error("Filter Exception with address", e2);
		} catch (JSONException e2) {
			Message.logger.error("Can't parse address", e2);
		}
		String hashlessAddress = null;
		try {
			hashlessAddress = address.hashlessAddress().toString();
		} catch (FilterException e1) {
			Message.logger.error("Hashless address error", e1);
		}
		for(int i=0; i<bannedUsers.length(); i++){
			// compare message address to the bannedUser addresses
			try {
				if(bannedUsers.get(i).equals(hashlessAddress)) return true;
			} catch (JSONException e) {
				Message.logger.error("Can't grab banned user", e);
			}
		}
		return false;
	}
	public Boolean containsBlacklistWords(){
		String content = null;
		try {
			content = (String) data.get("body");
		} catch (JSONException e) {
			Message.logger.error("Can't parse body", e);
		}
		ArrayList<String> words = getBlacklistWords();
		for(int i=0; i<words.size(); i++) {
			if(words.get(i).length() != 0 && content.toLowerCase().contains(words.get(i))){
				return true;
			}
		}
		return false;
	}
	@SuppressWarnings("unchecked")
	private ArrayList<String> getBlacklistWords(){
		ArrayList<String> words = new ArrayList<String>();
		// compare the current address to it
		JSONArray blacklist = null;
		try {
			blacklist = (JSONArray) getDomainRules().get("blacklist");
		} catch (JSONException e3) {
			Message.logger.error("Can't grab blacklist", e3);
		}
		for(int i=0; i<blacklist.length(); i++){
			try {
				words.add((String) blacklist.get(i));
			} catch (JSONException e) {
				Message.logger.error("Can't grab blacklist word", e);
			}
		}
		return words;
	}
	private static Pattern jabberAddress = Pattern.compile("^(.*)@(.*)$");
	@SuppressWarnings("unchecked")
	private ArrayList<String> getJabberUsers(){
		ArrayList<String> users = new ArrayList<String>();
		try{
			users = (ArrayList<String>) (new DBConnection() {
				public Object run(Object domainName) throws SQLException{
					String dn = (String) domainName;
					prepareStatement("Select jabber_id from users LEFT JOIN accesses ON accesses.user_id=users.id LEFT JOIN domains ON domains.id=accesses.domain_id WHERE domains.name = ?;");
					pstmt.setString(1, dn);
					executeQuery();
					ArrayList<String> users = new ArrayList<String>();
					String jid;
					while (rs.next()) {
						jid = rs.getString("jabber_id");
						if(jid != null){
						Matcher matcher = Message.jabberAddress.matcher(jid);
			            if(matcher.find()) 
			            	users.add(jid);
						}
			        }
					return users;
					
				}
			}).exec(to().domain());
		}catch(SQLException sqle){
			Message.logger.error("SQL Error", sqle);
		}
		return users;
	}
	public Session session(){
		Domain dn = Global.getDomain(from());
		Session s = (Session) dn.getChannel(from());
		return s;
	}
	public void sendJabberRequest(String from, String to, String message){
		try {
	        // Construct data
			String username = session().name();
			String fullmsg = username+": "+message;
	        String data = URLEncoder.encode("command", "UTF-8") + "=" + URLEncoder.encode("send", "UTF-8");
	        data += "&" + URLEncoder.encode("secret", "UTF-8") + "=" + URLEncoder.encode(Application.getProperty("messageSecret"), "UTF-8");
	        data += "&" + URLEncoder.encode("from", "UTF-8") + "=" + URLEncoder.encode(from, "UTF-8");
	        data += "&" + URLEncoder.encode("to", "UTF-8") + "=" + URLEncoder.encode(to, "UTF-8");
	        data += "&" + URLEncoder.encode("message", "UTF-8") + "=" + URLEncoder.encode(fullmsg, "UTF-8");
//        	System.out.println(data);
	        // Send data
	        URL url = new URL(Application.getProperty("url")+":"+Application.getProperty("openfirePort")+"/plugins/messageservice/service");
	        URLConnection conn = url.openConnection();
	        conn.setDoOutput(true);
	        OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
	        wr.write(data);
	        wr.flush();
	    
	        // Get the response
	        BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
	        wr.close();
	        rd.close();
	    } catch (Exception e) {
			Message.logger.error("Error sending to jabber", e);
	    }
	}
}
